/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package net.sourceforge.slcwsn.sensors;

import com.google.code.xbeejavaapi.api.XBee;
import java.util.HashMap;
import java.util.Map;
import net.sourceforge.slcwsn.event.LostSensorMessagesEvent;
import net.sourceforge.slcwsn.event.SensorChangedEvent;
import net.sourceforge.slcwsn.event.SensorMessageEvent;
import net.sourceforge.slcwsn.network.Network;
import org.apache.log4j.Logger;

/**
 *
 * @author David Miguel Antunes <davidmiguel [ at ] antunes.net>
 */
public abstract class Sensor {

    private static final Logger logger = Logger.getLogger(Sensor.class);
    protected long id;
    protected Network network;
    protected final long nSeqNumbers;
    protected final long maxMessageDelay;
    protected long currentSeqNum = 0;
    protected long lostMessages = 0;
    protected Map<Long, SensorMessageEvent> sensorMessageEvents = new HashMap<Long, SensorMessageEvent>();

    public Sensor(long id, Network network, long nSequenceNumbers, long maxMessageDelay) {
        this.nSeqNumbers = nSequenceNumbers;
        this.id = id;
        this.network = network;
        this.maxMessageDelay = maxMessageDelay;
    }

    public long getId() {
        return id;
    }

    public Network getNetwork() {
        return network;
    }

    /**
     * @return true if sequence number is ok, false if it is a repeated message
     */
    public void processSequenceNumber(long seqNum, long timestamp) {
        logger.debug("currentSeqNum=" + currentSeqNum);
        long newSeqNumDistance =
                (seqNum >= (currentSeqNum % nSeqNumbers))
                ? seqNum - (currentSeqNum % nSeqNumbers)
                : nSeqNumbers - (currentSeqNum % nSeqNumbers) + seqNum;

        long oldSeqNumDistance =
                (seqNum < (currentSeqNum % nSeqNumbers))
                ? (currentSeqNum % nSeqNumbers) - seqNum
                : (currentSeqNum % nSeqNumbers) + nSeqNumbers - seqNum;

        if (oldSeqNumDistance > maxMessageDelay) {
            if (newSeqNumDistance == 0) {
                logger.debug("Repeated Message");
            } else {
                if (newSeqNumDistance > 1) {
//                    LostSensorMessagesEvent lostSensorMessagesEvent =
//                            new LostSensorMessagesEvent(timestamp, this, (int) newSeqNumDistance - 1, network);
//                    lostMessages += (long) newSeqNumDistance - 1;
//                    network.newEvent(lostSensorMessagesEvent);
                    logger.debug("May have lost " + (newSeqNumDistance - 1) + " messages from sensor " + this);
                }
                currentSeqNum += newSeqNumDistance;
            }
        } else {
            logger.trace("Received an old event");
        }
        logger.debug("seqNum=" + seqNum);
        logger.debug("new currentSeqNum=" + currentSeqNum);

        network.newEvent(new SensorChangedEvent(System.currentTimeMillis(), network, this));
    }

    public SensorMessageEvent eventFactory(SensorMessageEvent tentative, XBee superNode) {
        if (sensorMessageEvents.containsKey(tentative.getSensorEventSequenceNumber())) {
            SensorMessageEvent existing = sensorMessageEvents.get(tentative.getSensorEventSequenceNumber());
            existing.getDetectedBy().add(superNode);
            return existing;
        } else {
            sensorMessageEvents.put(tentative.getSensorEventSequenceNumber(), tentative);
            tentative.getDetectedBy().add(superNode);
            return tentative;
        }
    }

    public long getCurrentSeqNum() {
        return currentSeqNum;
    }

    public long getLostMessages() {
        return (currentSeqNum + 1) - sensorMessageEvents.size();
    }

    public Map<Long, SensorMessageEvent> getSensorEvents() {
        return sensorMessageEvents;
    }

    public abstract void accept(SensorVisitor visitor);

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Sensor other = (Sensor) obj;
        if (this.id != other.id) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        int hash = 5;
        hash = 29 * hash + (int) (this.id ^ (this.id >>> 32));
        return hash;
    }
}
